import "../../lib/ramda.js";
import utils from "../../lib/utils.js";
import game from "./game.js";
import ux from "./ux.js";

const Consts = {
    FPS:10,
    DEFAULT_KVS:{
        "[ss]local-port": 8082,
        "[ss]uct-times": 4096,
        "[ss]level": 1,
    },
}

const State = {
    game_state: new game.State(),
    records: [],
    record_redos: [],
    ai_player: [false, false],
    ai_in_think: false,
    ai_status: "",
    ai_said: [game.PIECE_EMPTY, ""],
    ai_engine_name: "",
    ai_params: new Map([
        ["uct-times", undefined],
        ["level", undefined],
    ]),
}
const AiManager = (()=>{
    const me = {}
    const _observers = []
    function make(module){
        const worker = new Worker(`./${module}.js`, {type: "module"})
        worker.onmessage = (e)=>{
            const msg = e.data
            _observers.forEach(R.applyTo(msg))
        }
        worker.onerror = (e)=>{
            console.log(`AI-Error(${e.filename}[${e.lineno}]): ${e.message}`, e)
            State.ai_in_think = false
            State.ai_player = [false, false]
        }
        return worker
    }
    let _engine = make("ai-engine")
    me.listen = (f)=>{
        _observers.push(f)
        return me
    }
    me.send  = (...tokens)=>{
        const text = R.map(String, tokens).join(' ')
        _engine.postMessage(text)
        return me
    }
    me.restart = ()=>{
        _engine.postMessage("reset")
        return me
    }
    return me
})()

function random_game(min_np, max_np) {
    const gs = new game.State()
    let np = Math.floor(Math.random() * (1+max_np-min_np)) + min_np
    while ((np > 0) && (! gs.has_over())) {
        const moves = gs.board().moves(gs.pcur())
        for (const loc of R.range(0,game.BOARD_LEN)) {
            const is_can_move = gs.board().can_move(gs.pcur(), loc)
            if (is_can_move) {
                if (moves.indexOf(loc) < 0) {
                    console.log(`rg-fail can move but not in moves pcur:${gs.pcur()} loc:${loc} moves:${moves}`)
                    game_state.reset(gs)
                    return null
                }
            } else if (moves.indexOf(loc) >= 0) {
                console.log(`rg-fail can't move but in moves pcur:${gs.pcur()} loc:${loc} moves:${moves}`)
                game_state.reset(gs)
                return null
            }
        }
        if (moves.length > 0) {
            const loc = moves[Math.floor(Math.random() * moves.length)]
            gs.down(loc, true)
            np -= 1
        }
    }
    return gs
}
    window.rg = function(){
        game_state.reset(random_game(4,100))
    }

;(() => {
    const game_state = State.game_state
    const io_fns = {
        kvs:{
            _f(t, k, v=undefined) {
                const k_fix = `[ss]${k}`
                if (R.isNil(v)) {
                    return t[k_fix]
                } else {
                    t[k_fix] = v
                }
            },
            text(k, v=undefined){return io_fns.kvs._f(utils.redis.text, k, v)},
            number(k, v=undefined){return io_fns.kvs._f(utils.redis.number, k, v)},
            bool(k, v=undefined){return io_fns.kvs._f(utils.redis.bool, k, v)},
        },
        _init(){
            State.ai_params.set("uct-times", io_fns.kvs.number("uct-times"))
            State.ai_params.set("level", io_fns.kvs.number("level"))
            return io_fns
        },
        for_board(fn){
            for (const loc of R.range(0, game.BOARD_LEN)) {
                fn(loc, game_state.board().see(loc), game_state.can_down(loc))
            }
            return io_fns
        },
        see_board(loc){
            return game_state.board().see(loc)
        },
        pcur(){
            return game_state.pcur()
        },
        has_over(){
            return game_state.has_over()
        },
        n_black(){
            return game_state.board().n_black()
        },
        n_white(){
            return game_state.board().n_white()
        },
        max_piece(){
            return game_state.board().max_piece()
        },
        record_top(){
            return game_state.record_top()
        },
        ai_in_think(){
            return State.ai_in_think
        },
        ai_status(){
            return State.ai_status
        },
        ai_said(){
            return State.ai_said
        },
        ai_player(pcur){
            const pid = ((pcur == game.PIECE_BLACK) ? 0 : 1)
            return State.ai_player[pid]
        },
        ai_player_set(pcur, use){
            const pid = ((pcur == game.PIECE_BLACK) ? 0 : 1)
            State.ai_player[pid] = use
            return io_fns
        },
        ai_param(name){
            return State.ai_params.get(name)
        },
        ai_param_set(name, val){
            if (State.ai_params.has(name)) {
                AiManager.send(`set`, name, val)
                State.ai_params.set(name, val)
                io_fns.kvs.text(name, val)
            }
            return io_fns
        },
        ai_engine_name(){
            return State.ai_engine_name
        },
        extra(name=undefined){
            function rg(a,b) {
                State.ai_player = [false, false]
                State.ai_said = [game.PIECE_EMPTY, ""]
                State.records = []
                State.record_redos = []
                game_state.reset(random_game(a-4,b-4))
            }
            const fns = {
                "use-js": ()=>{AiManager.send("switch-engine", "js")},
                "use-wasm": ()=>{AiManager.send("switch-engine", "wasm")},
                "use-local": ()=>{AiManager.send("switch-engine", "local")},
                "random-game": ()=>{rg(4,100)},
                "random-game-mid": ()=>{rg(65,66)},
            }
            if (R.is(String,name) && (! State.ai_in_think)) {
                if (fns.hasOwnProperty(name)) {
                    fns[name]()
                }
            }
        },
        down(loc){
            let save = new game.State(game_state)
            if (game_state.down(loc)) {
                if (game_state.board().moves(game_state.pcur()).length < 1) {
                    game_state.pass()
                }
                State.records.push(save)
                State.record_redos = []
            } else {
                save = 0
                if (State.ai_in_think) {
                    io_fns.ai_player_set(io_fns.pcur(), false)
                    console.log("ai invalid loc", loc)
                }
            }
            return io_fns
        },
        on_tap_piece(loc){
            if (State.ai_in_think) {return}
            if (game_state.has_over()) {return}
            return io_fns.down(loc)
        },
        on_cmd_reset(){
            if (State.ai_in_think) {return}
            game_state.reset()
            State.records = []
            State.record_redos = []
            State.ai_in_think = false
            State.ai_said = [game.PIECE_EMPTY, ""]
            AiManager.restart()
            console.clear()
            return io_fns
        },
        on_cmd_undo(){
            if (State.ai_in_think) {return}
            if (game_state.has_over()) {
                io_fns.ai_player_set(game.PIECE_BLACK, false); io_fns.ai_player_set(game.PIECE_WHITE, false)
            }
            if (State.records.length > 0) {
                State.ai_player = [false, false]
                State.record_redos.push(new game.State(game_state))
                game_state.reset(State.records.at(-1))
                State.records.pop()
                State.ai_said = [game.PIECE_EMPTY, ""]
            }
        },
        on_cmd_redo(){
            if (State.ai_in_think) {return}
            if (game_state.has_over()) {return}
            if (State.record_redos.length > 0) {
                State.records.push(new game.State(game_state))
                game_state.reset(State.record_redos.at(-1))
                State.record_redos.pop()
            }
        },
        on_cmd_load_first(){
            if (State.ai_in_think) {return}
            while (State.records.length > 0) {
                io_fns.on_cmd_undo()
            }
        },
        on_cmd_load_last(){
            if (State.ai_in_think) {return}
            while (State.record_redos.length > 0) {
                io_fns.on_cmd_redo()
            }
        },
        on_reload(){
            AiManager.send("raii")
        }
    }

    const AiMessage = {}
    AiMessage["ready"] = ()=>{
        for (const kv of State.ai_params.entries()) {
            AiManager.send("set", kv[0], kv[1])
        }
        AiManager.send("ready-local", io_fns.kvs.text("local-port"))
    }
    AiMessage["step"] = (a)=>{
        if ("pass" == a) {
            State.game_state.pass()
        } else {
            const loc = Number(a)
            if (game.loc_in_range(loc)) {
                if (io_fns.see_board(loc) == game.PIECE_EMPTY) {
                    io_fns.down(loc)
                } else {
                    console.log("ai loc not be empty", loc)
                }
            } else {
                console.log("ai invalid loc:", loc)
            }
        }
        State.ai_in_think = false
    }
    AiMessage["status"] = (...a)=>{
        State.ai_status = a.join(' ')
    }
    AiMessage["say"] = (...a)=>{
        if (State.ai_player[0] && State.ai_player[1]) {
            State.ai_said = [game.PIECE_EMPTY, ""]
        } else {
            State.ai_said = [io_fns.pcur(), a.join(' ')]
        }
    }
    AiMessage["ok"] = (...a)=>{
        console.log("ok", ...a)
    }
    AiMessage["used-engine"] = (...a)=>{
        const name = a.join(' ')
        if ("websocket" == name) {
            AiManager.send("websocket-heart")
        }
        State.ai_engine_name = name
    }
    AiMessage["ei-report"] = AiMessage["ok"]
    AiMessage["ei-view-board"] = (...a)=>{
        function make_p(str) {
            const n = Number(str)
            return ((n == 1) ? game.PIECE_BLACK : ((n == 2) ? game.PIECE_WHITE : game.PIECE_EMPTY))
        }
        const board_str = a.join(' ')
        if (board_str.length >= game.BOARD_LEN) {
            const board = new game.Board()
            for (const i of R.range(0,game.BOARD_LEN)) {
                const p = make_p(board_str[i])
                board._set(i, p)
            }
            game_state.reset()
            if (board_str.length > game.BOARD_LEN) {
                const pcur_str = board_str[game.BOARD_LEN]
                game_state._pcur = ((pcur_str == "1") ? game.PIECE_BLACK : ((pcur_str == "2") ? game.PIECE_WHITE : game.PIECE_BLACK))
            }
            game_state.board().reset(board)
        }
    }
    AiManager.listen((msg)=>{
        const tokens = msg.split(' ')
        if (tokens.length > 0) {
            const head = tokens[0]
            if (AiMessage.hasOwnProperty(head)) {
                AiMessage[head](...(tokens.slice(1)))
            } else {
                console.log("[Unknown-Message]", msg)
            }
        }
    })

    const on_frame = ()=>{
        if (State.ai_in_think) {return}
        if (game_state.has_over()) {return}
        AiManager.send("used-engine")
        if (((game_state.pcur() == game.PIECE_BLACK) && (State.ai_player[0])) || (game_state.pcur() == game.PIECE_WHITE) && (State.ai_player[1])) {
            State.ai_status = ""
            AiManager.send("step", game.piece_to_str(game_state.pcur()), game_state.board().to_str())
            State.ai_in_think = true
        }
    }
    utils.redis.init({
        on_key_notfound(k){return Consts.DEFAULT_KVS[k]}
    })
    ux.init(io_fns._init())
    utils.clock.frame_watch(ux.on_frame).frame_watch(on_frame).start(Consts.FPS)
})()

